#ifndef __ZPP_LOGGING_HPP__
#define __ZPP_LOGGING_HPP__

#include "zephyr/sys/timeutil.h"
#include <source_location>
#include <string_view>
#include <zephyr/kernel.h>
#include <zpp/fmt.hpp>

namespace zpp {

enum class Level { off, error, warn, info, debug };
template <Level kLevel>
  requires(kLevel > Level::off)
constexpr auto LevelString() -> std::string_view {
  switch (kLevel) {
  case Level::error:
    return "err";
  case Level::warn:
    return "warn";
  case Level::info:
    return "inf";
  case Level::debug:
    return "dbg";
  }
  return "UNDEFINED";
}

namespace logging {

namespace internal {
static constexpr auto kNoDefined = -1;
}
} // namespace logging

#define ZPP_LOG_LEVEL(level)                                                   \
  COND_CODE_1(CONFIG_LOG, CONFIG_##level##_LOG_LEVEL,                          \
              zpp::logging::internal::kNoDefined)

class logger {
public:
#if CONFIG_LOG
#if CONFIG_LOG_OVERRIDE_LEVEL > 0
  constexpr static auto k_log_level = CONFIG_LOG_OVERRIDE_LEVEL;
#else
  constexpr static Level k_log_level =
      static_cast<Level>(CONFIG_LOG_DEFAULT_LEVEL);
#endif
#else
  constexpr static auto k_log_level = zpp::logging::internal::kNoDefined;
#endif
  consteval logger(const char *module_name,
                   int level = static_cast<int>(k_log_level))
      : m_module_name(module_name), m_level(static_cast<Level>(level)) {}
  template <typename... t_args> auto info(const char *fmt, t_args... args) {
    this->print<Level::info>(fmt, args...);
  }
  template <typename... t_args>
  auto info_if(bool cond, const char *fmt, t_args... args) {
    return this->print_if<Level::info>(cond, fmt, args...);
  }
  template <typename... t_args> auto warn(const char *fmt, t_args... args) {
    this->print<Level::warn>(fmt, args...);
  }
  template <typename... t_args>
  auto warn_if(bool cond, const char *fmt, t_args... args) {
    return this->print_if<Level::warn>(cond, fmt, args...);
  }
  template <typename... t_args> auto debug(const char *fmt, t_args... args) {
    this->print<Level::debug>(fmt, args...);
  }
  template <typename... t_args>
  auto debug_if(bool cond, const char *fmt, t_args... args) {
    return this->print_if<Level::debug>(cond, fmt, args...);
  }
  template <typename... t_args> auto error(const char *fmt, t_args... args) {
    this->print<Level::error>(fmt, args...);
  }
  template <typename... t_args>
  auto error_if(bool cond, const char *fmt, t_args... args) {
    return this->print_if<Level::error>(cond, fmt, args...);
  }

protected:
  template <Level kLevel, typename... TArgs>
  auto print(const char *fmt, TArgs... args) const -> void {
  #if CONFIG_LOG
    if ((kLevel) <= m_level) {
      zpp::print("[{}] <{}> {}: ", k_uptime_ticks(), LevelString<kLevel>(),
                 m_module_name);
      zpp::println(fmt, args...);
    }
  #endif
  }
  template <Level kLevel, typename... TArgs>
  [[maybe_unused]] auto print_if(bool cond, const char *fmt, TArgs... args) {
    if (cond) {
      print<kLevel>(fmt, args...);
    }
    return Cond{*this, cond};
  }
  class Cond {
  public:
    constexpr Cond(logger &log_base, bool cond)
        : m_log_base(log_base), m_cond(cond) {}
    template <typename... TArgs>
    auto or_else(const char *fmt, TArgs... args) const {
      if (!m_cond) {
        m_log_base.print(fmt, args...);
      }
    };

  private:
    logger &m_log_base;
    bool m_cond;
  };

private:
  std::string_view m_module_name;
  Level m_level;
};

} // namespace zpp
#endif //__ZPP_LOGGING_HPP__
